home *** CD-ROM | disk | FTP | other *** search
- {
- Next in this continuing series of code: the actual directry
- object.
- }
-
- Unit Dirs;
- {
- A directory management object from a concept originally by Allan
- Holub, as discussed in Byte Dec/93 (Vol 18, No 13, page 213)
-
- Turbo Pascal code by Larry Hadley, tested using BP7.
- }
- INTERFACE
-
- Uses Sort, DOS;
-
- TYPE
- pSortSR = ^oSortSR;
- oSortSR = OBJECT(oSortTree)
- procedure DeleteNode(var Node); virtual;
- end;
-
- callbackproc = procedure(name :string; lev :integer);
-
- prec = ^searchrec;
-
- pentry = ^entry;
- entry = record
- fil :prec;
- next, last :pentry;
- end;
-
- pdir = ^dir;
- dir = record
- flist :pentry;
- count :word;
- path :string[80];
- end;
-
- pDirectry = ^Directry;
- Directry = OBJECT
- dirroot :pdir;
-
- constructor Init(path, filespec :string; attribute :byte);
- destructor Done;
-
- procedure Load(path, filespec :string; attribute :byte);
- procedure Sort;
- procedure Print;
- END;
-
- CONST
- NotDir = ReadOnly+Hidden+SysFile+VolumeID+Archive;
- dosattr : array[0..8] of char = '.rhsvdaxx';
-
- procedure TraverseTree(root :string; pcallproc :pointer; do_depth :boolean);
-
- IMPLEMENTATION
-
- var
- treeroot :pSortSR; { sorting tree object }
-
- procedure disposelist(ls :pentry);
- var
- lso :pentry;
- begin
- while ls<>NIL do
- begin
- dispose(ls^.fil);
- lso := ls;
- ls := ls^.next;
- dispose(lso);
- end;
- end;
-
- { Define oSortSR.DeleteNode method so object knows how to dispose of
- individual data pointers in the event that "Done" is called before
- tree is empty. }
- procedure oSortSR.DeleteNode(var Node);
- var
- pNode :pRec ABSOLUTE Node;
- begin
- dispose(pNode);
- end;
-
- constructor Directry.Init(path, filespec :string; attribute :byte);
- var
- pathspec :string;
- node :pentry;
- i :word;
- BEGIN
- FillChar(Self, SizeOf(Self), #0);
- Load(path, filespec, attribute); { scan specified directory }
- if dirroot^.count=0 then { if no files were found, abort }
- begin
- if dirroot<>NIL then
- begin
- disposelist(dirroot^.flist);
- dispose(dirroot);
- end;
- FAIL;
- end;
- { the following code expands the pathspec to a full qualified path }
- pathspec := dirroot^.path+'\';
- node := dirroot^.flist;
- while ((node^.fil^.name='.') or (node^.fil^.name='..')) and
- (node^.next<>NIL) do
- node := node^.next;
- if node^.fil^.name='..' then
- pathspec := pathspec+'.'
- else
- pathspec := pathspec+node^.fil^.name;
- pathspec := FExpand(pathspec);
- i := Length(pathspec);
- repeat
- Dec(i);
- until (i=0) or (pathspec[i]='\');
- if i>0 then
- begin
- Delete(pathspec, i, Length(pathspec));
- dirroot^.path := pathspec;
- end;
- END;
-
- destructor Directry.Done;
- begin
- if dirroot<>NIL then
- begin
- disposelist(dirroot^.flist);
- dispose(dirroot);
- end;
- end;
-
- procedure Directry.Load(path, filespec :string; attribute :byte);
- { scan a specified directory with a specified wildcard and attribute
- byte }
- var
- count : word;
- pstr : pathstr;
- dstr : dirstr;
- srec : SearchRec;
- dirx : pdir;
- firstfl, thisfl, lastfl : pentry;
- begin
- count := 0;
- New(firstfl);
- with firstfl^ do
- begin
- next := NIL; last := NIL; New(fil);
- end;
- thisfl := firstfl; lastfl := firstfl;
- dstr := path;
- if path = '' then dstr := '.';
- if dstr[Length(dstr)]<>'\' then dstr := dstr+'\';
- if filespec = '' then filespec := '*.*';
- pstr := dstr+filespec;
-
- FindFirst(pstr, attribute, srec);
- while DosError=0 do { while new files are found... }
- begin
- if srec.attr = (srec.attr and attribute) then
- { make sure the attribute byte matches our required atttribute mask }
- begin
- if count>0 then
- { if this is NOT first file found, link in new node }
- begin
- New(thisfl);
- lastfl^.next := thisfl;
- thisfl^.last := lastfl;
- thisfl^.next := NIL;
- New(thisfl^.fil);
- lastfl := thisfl;
- end;
- thisfl^.fil^ := srec;
- Inc(count);
- end;
- FindNext(srec);
- end;
- { construct root node }
- New(dirx);
- with dirx^ do
- flist := firstfl;
- dirx^.path := path; { path specifier for directory list }
- dirx^.count := count; { number of files in the list }
-
- if dirroot=NIL then
- dirroot := dirx
- else
- begin
- disposelist(dirroot^.flist);
- dispose(dirroot);
- dirroot := dirx;
- end;
- end;
-
- { The following function is the far-local function needed for the
- SORT method (which uses the sort unit posted earlier)
- Note that this is hard-coded to sort by filename, then extension.
- I plan to rewrite this later to allow user-selectable sort
- parameters and ordering. }
- function Comp(d1, d2 :pointer):integer; far;
- var
- data1 :pRec ABSOLUTE d1;
- data2 :pRec ABSOLUTE d2;
- name1, name2, ext1, ext2 :string;
- begin
- { This assures that the '.' and '..' dirs will always be the first
- listed. }
- if (data1^.name='.') or (data1^.name='..') then
- begin
- Comp := -1;
- EXIT;
- end;
- if (data2^.name='.') or (data2^.name='..') then
- begin
- Comp := 1;
- EXIT;
- end;
- with data1^ do
- begin
- name1 := Copy(name, 1, Pos('.', name)-1);
- ext1 := Copy(name, Pos('.', name)+1, 3);
- end;
- with data2^ do
- begin
- name2 := Copy(name, 1, Pos('.', name)-1);
- ext2 := Copy(name, Pos('.', name)+1, 3);
- end;
- if name1=name2 then
- { If filename portion is equal, use extension to resolve tie }
- begin
- if ext1=ext2 then
- { There should be NO equal filenames, but handle anyways for
- completeness... }
- Comp := 0
- else
- if ext1>ext2 then
- Comp := 1
- else
- Comp := -1;
- end
- else
- if name1>name2 then
- Comp := 1
- else
- Comp := -1;
- end;
-
- { Sort method uses the sort unit to sort the collected directory
- entries. }
- procedure Directry.Sort;
- var
- s1, s2 :string;
- p1 :pentry;
-
- { This local procedure keeps code more readable }
- procedure UpdatePtr(var prev :pentry; NewEntry :pointer);
- begin
- if NewEntry<>NIL then { check to see if tree is empty }
- begin
- New(prev^.next);
- prev^.next^.fil := NewEntry;
- prev^.next^.last := prev;
- prev := prev^.next;
- prev^.next := NIL;
- end
- else
- prev := prev^.next;
- { tree is empty, flag "done" with NIL pointer }
- end;
-
- begin
- p1 := dirroot^.flist;
- New(treeroot, Init(Comp));
- { Create a sort tree, point to our COMP function }
- while p1<>NIL do
- { Go through our linked list and insert the items into the sorting
- tree, dispose of original nodes as we go. }
- begin
- if p1^.last<>NIL then
- dispose(p1^.last);
- treeroot^.InsertNode(p1^.fil);
- if p1^.next=NIL then
- begin
- dispose(p1);
- p1 := NIL;
- end
- else
- p1 := p1^.next;
- end;
- { Reconstruct directory list from sorted tree }
- New(dirroot^.flist);
- with dirroot^ do
- begin
- flist^.next := NIL;
- flist^.last := NIL;
- flist^.fil := treeroot^.ReadLeftNode;
- end;
- if dirroot^.flist^.fil<>NIL then
- begin
- p1 := dirroot^.flist;
- while p1<>NIL do
- UpdatePtr(p1, treeroot^.ReadLeftNode);
- end;
- { We're done with sorting tree... }
- dispose(treeroot, Done);
- end;
-
- procedure Directry.Print;
- { currently prints the entire list, may modify this later to allow
- selective printing }
- var
- s, s1 :string;
- e :pentry;
- dt :DateTime;
- dbg :byte;
-
- procedure DoDateEle(var sb :string; de :word);
- begin
- Str(de, sb);
- if Length(sb)=1 then { Add leading 0's}
- sb := '0'+sb;
- end;
-
- begin
- if dirroot=NIL then EXIT; { make sure empty dirs aren't attempted }
- e := dirroot^.flist;
- while e<>NIL do
- begin
- s := '';
- with e^.fil^ do
- begin
- dbg := 1;
- repeat
- case dbg of { parse attribute bits }
- 1: s := s+dosattr[(attr and $01)];
- 2: s := s+dosattr[(attr and $02)];
- 3: if (attr and $04) = $04 then
- s := s+dosattr[3]
- else
- s := s+dosattr[0];
- 4: if (attr and $08) = $08 then
- s := s+dosattr[4]
- else
- s := s+dosattr[0];
- 5: if (attr and $10) = $10 then
- s := s+dosattr[5]
- else
- s := s+dosattr[0];
- 6: if (attr and $20) = $20 then
- s := s+dosattr[6]
- else
- s := s+dosattr[0];
- else
- s := s+dosattr[0];
- end;
- Inc(dbg);
- until dbg>8;
- s := s+' ';
- { Kludge to make sure that extremely large files (>=100MB) don't
- overflow size field... }
- if size<100000000 then
- Str(size:8, s1)
- else
- begin
- Str((size div 1000):7, s1); { decimal kilobytes }
- s1 := s1+'k';
- end;
- s := s+s1+' ';
- { Format date/time fields }
- UnpackTime(Time, dt);
- {month}
- DoDateEle(s1, dt.month); s := s+s1+'/';
- {day}
- DoDateEle(s1, dt.day); s := s+s1+'/';
- {year}
- DoDateEle(s1, dt.year); s := s+s1+' ';
- {hour}
- DoDateEle(s1, dt.hour); s := s+s1+':';
- {minutes}
- DoDateEle(s1, dt.min); s := s+s1+':';
- {seconds}
- DoDateEle(s1, dt.sec); s := s+s1+' - ';
- s := s+dirroot^.path+'\'+name;
- end;
- Writeln(s); s := '';
- e := e^.next;
- end;
- Writeln; Writeln(' ', dirroot^.count, ' files found.'); Writeln;
- end;
-
- { If TraverseTree is not given a callback procedure, this one is
- used. }
- procedure DefaultCallback(name :string; lev :integer); far;
- var
- s :string;
- const
- spaces = ' ';
- begin
- s := Copy(spaces, 1, lev*4); s := s+name;
- Writeln(s);
- end;
-
- { TraverseTree is untested as yet, rest of code (above) works fine.
- Note that TraverseTree is NOT a member method of DIRECTRY. Read
- the BYTE Dec/93 article for a clarification of why it is good
- that it not be a member.}
- procedure TraverseTree(root :string; pcallproc :pointer; do_depth :boolean);
- var
- level :integer;
- fullpath :string;
- rootdir :pdir;
- const
- callproc : callbackproc = DefaultCallBack;
-
- { Actual recursive procedure to scan down directory structure
- using the DIRECTRY object. }
- procedure Tree(newroot :string; callee :callbackproc; do_last :boolean);
- var
- subdirs :pdirectry;
- direntry :pentry;
-
- Procedure DoDir;
- begin
- New(subdirs, Init(newroot, '*.*', NotDir));
- if subdirs<>NIL then
- begin
- subdirs^.sort;
- direntry := subdirs^.dirroot^.flist;
- while direntry<>NIL do
- begin
- fullpath := newroot+'\'+direntry^.fil^.name;
- callee(newroot, level);
- direntry := direntry^.next;
- end;
- dispose(subdirs, done);
- end;
- end;
-
- begin
- if not(do_last) then
- DoDir;
-
- New(subdirs, Init(newroot, '*.*', directory));
-
- if subdirs<>NIL then
- begin
- subdirs^.sort;
- direntry := subdirs^.dirroot^.flist;
- while direntry<>NIL do
- begin
- Inc(level);
- fullpath := newroot+'\'+direntry^.fil^.name;
- Tree(fullpath, callee, do_last);
- dec(level);
- direntry := direntry^.next;
- end;
- dispose(subdirs, done);
- end;
-
- if do_last then
- DoDir;
- end;
-
- begin
- level := 0;
-
- if pcallproc<>NIL then
- callproc := callbackproc(pcallproc^);
-
- root := fexpand(root);
- if root[Length(root)]='\' then
- Delete(root, Length(root), 1);
-
- if not(do_depth) then
- callproc(root, level);
-
- Tree(root, callproc, do_depth);
-
- if do_depth then
- callproc(root, level);
- end;
-
- END.